<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>进阶：14、Render函数 使用Javascript代替模板功能</title>
	<script type="text/javascript" src="https://unpkg.com/vue/dist/vue.js"></script>
	<style type="text/css">
	.btnContainer{ width: 600px; height: 200px; background-color: gray; padding: 5px; }
	.sonContainer{ width: 300px; height: 100px; background-color: red; float: right; }
	</style>
</head>
<body>
	<div id="app1">
		<h3>v-if 和 v-for</h3>
		<render-template :items="items"></render-template>
	</div><br/>

	<div id="app2">
		<h3>v-model</h3>
		<v-model-template :value="value"></v-model-template>
	</div><br/>

	<div id="app3">
		<h3>事件 & 按键修饰符</h3>
		<event-template></event-template>
	</div><br/>

	<div id="app4">
		<h3>slots</h3>
		<slots-template1>
			<p>test slots</p>
		</slots-template1>
		<slots-template2>
			<!-- 这里的代码看不懂回见基础篇【slot作用域插槽】 -->
			<template scope="props">
				<span>{{ props.text }}</span>
			</template>		
		</slots-template2><br/>
		<slots-template3 text="slots demo3">			
		</slots-template3>
	</div>

	<script type="text/javascript">
		// 使用render函数来实现 v-if + v-for 实现的效果
		// <ul v-if="items.length">
		//   <li v-for="item in items">{{ item.name }}</li>
		// </ul>
		// <p v-else>No items found.</p>	
		// 	
		Vue.component('render-template',{
			props: ['items'],
			render: function(createElement){
				if(this.items.length){
					return createElement('ul',this.items.map(function(item){
						return createElement('li', item.name)
					}))
				}else{
					return createElement('p', 'No items found.')
				}
			}
		})
		var vm1 = new Vue({
			el: '#app1',
			data: {
				items: [
					{ name: 'name1' },
					{ name: 'name2' },
					{ name: 'name3' },
					{ name: 'name4' }
				]
			}
		})

		// render里不能直接给创建的input元素使用 v-model 进行数据双向绑定
		// 必须自行实现逻辑
		Vue.component('v-model-template',{
			render: function(createElement){
				var self = this
				return createElement('input',{
					domProps: {
						value: self.value
					},
					on: {
						input: function(event){
							self.value = event.target.value
						}
					}
				})
			}
		})
		var vm2 = new Vue({
			el: '#app2',
			data: {
				value: 'v-model-template'
			}
		})

		// ------------------------- 事件 & 按键修饰符 ---------------------------
		// 这个很难说清楚，直接上demo吧
		// 定义一个用render生成元素的组件
		Vue.component('event-template',{
			render: function(createElement){
				return createElement('div',
					{
						'class': {
							btnContainer: true
						},
						on: {
							// 两个可应用于on的修饰符:
							// (1) 一个是! , 表示事件采用捕获模式
							// (2) 另一个是~ , 表示事件只触发一次
							// 可以组合起来一起使用，效果就是只触发一次的事件捕获模式
							click: this.test1,							
						}
					},
					[
						createElement('p','我是外层的div，点击我会触发click事件从而执行test1方法'),
						createElement('input',
							{
								style: {
									margin: '10px'
								},
								domProps: {
									title: '点击我'
								},
								on: {
									click: function(event){
										// 这里起到阻止事件冒泡的作用
										// 注释它则会触发div的test1回调函数
										// 作用等同于基础篇【事件处理器】中的.stop事件修饰符
										event.stopPropagation()
										alert('我加了.stop事件修饰符可以组织点击事件向父级div传播')
									},
									// key就是按键的意思，up抬起的意思，合在一起keyup就是抬起按键的意思
									// 只要对input聚焦按下键盘上的任意按键然后抬起来就会触发test2回调函数
									// 加了~就是指触发一次，按第二次抬起来是不会在触发回调函数了
									'~keyup': this.test2
								}
							}
						),
						createElement('a',
							{
								domProps: {
									href: 'https://www.baidu.com'
								},
								on: {
									click: function(event){
										// 阻止点击事件向父级div传播
										event.stopPropagation()
										// 阻止a标签点击后跳转链接的默认行为
										// 作用等同于基础篇【事件处理器】中的.prevent事件修饰符
										event.preventDefault()
										alert('我被点击了')
									}

								}
							},
							'百度一下'
						),
						createElement('div',
							{
								'class': {
									sonContainer: true
								},
								on: {
									// 这里我有点不太明白了，说'!'是采用事件捕获模式
									// 但是按照事件捕获模式应该是先触发外层div的点击回调函数test1再
									// 执行这里的alert，但事实还是事件冒泡模式？有待解决囧
									'!click': function(event){
										alert('你点击了红色区域')
									}
								}
							}
						)
					]					
				)
			},
			methods: {
				test1: function(){
					alert('you click the div')
				},
				test2: function(){
					alert('you keyup a key')
				}
			}
		})
		var vm3 = new Vue({
			el: '#app3'
		})

		// -------------------------------- slots ---------------------------------
		// 下面的例子演示如何在render中使用slots
		// 等同于template: '<div><slot></slot></div>'
		Vue.component('slots-template1',{
			render: function(createElement){
				return createElement('div',this.$slots.default)
			}
		})
		// 等同于template: '<div><slot text="slot demo2"></slot></div>'
		Vue.component('slots-template2',{
			data: function(){
				return { msg: 'slot demo2' }
			},
			render: function(createElement){
				return createElement('div',[
					this.$scopedSlots.default({
						text: this.msg
					})
				])
			}
		})
		// 想要将具名slot传递给子组件，可用creatElement可选对象参数中的scopedSlots属性
		Vue.component('child',{
			props: ['msg'],
			// 可以通过给slot元素绑定属性传给插入到父组件中子组件内的数据模板
			// 注意这里传递数据的方式：slot绑定的多个属性是合起来成一个对象传递给插入的内容的
			// 比如子组件的slot中绑定了多个属性
			// <slot key1="val1" key2="val2" key3="val3"></slot>
			// 那么通过<template scope="props">标签的scope属性将数据传递给插入内容时，传递的数据
			// 对象props = { key1:'val1', key2:'val2', key3:'val3' }
			template: '<div><span>child test {{ msg }}</span><br/><slot text="msg" text2="msg2"></slot></div>',			
		})
		Vue.component('slots-template3',{
			props: ['text'],
			render: function(createElement){
				return createElement('child',
					{
						props: {
							msg: 'slot demo3'
						},
						// 在demo13中还未能完全理解的scopedSlots
						// 在这里可以解释它的作用了，就是向子组件child的slot中插入内容
						// 并且通过props传递数据						
						scopedSlots: {
							// 以下两种写法作用相同：
							// 写法1
							// default: function(props){
							// 	// return createElement('span', 'pass to child')
							// 	return createElement('span', props.text)
							// 	// return createElement('span', props.text2)
							// }
							
							// 写法2
							default: props => createElement('span', props.text2)
						}						
					}
				)
			}
		})
		var vm4 = new Vue({
			el: '#app4'
		})
	</script>
</body>
</html>